/*
 * ArchiveDeployer.java July 2009
 *
 * Copyright (C) 2009, Quirino Zagarese <quirino@laszloitalia.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA  02111-1307  USA
 */
package org.eu4ria.core;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javassist.NotFoundException;

import org.eu4ria.AppModule;
import org.eu4ria.Application;
import org.eu4ria.annotations.Module;

public class ArchiveDeployer {

	private AppRepository apps = AppRepository.getInstance();

	private EU4RIAClassLoader appsClassLoader = EU4RIAClassLoader.getInstance();

	private String appsScanPath = null;

	private ModuleDeployer moduleDeployer = ModuleDeployer.getInstance();

	private static ArchiveDeployer instance;

	private Vector<File> appsJarFiles = new Vector<File>();

	private ArchiveDeployer() {

	}

	public static ArchiveDeployer getInstance() {
		if (instance == null) {
			instance = new ArchiveDeployer();
		}
		return instance;
	}

	public void deployApps() {
		int size = appsJarFiles.size();
		for (int i = 0; i < size; i++) {
			deployApplication(appsJarFiles.elementAt(i), true);
		}
	}

	@SuppressWarnings("unchecked")
	public boolean deployApplication(File jar, boolean instantiate) {
		if (!appsJarFiles.contains(jar)) {
			appsJarFiles.addElement(jar);
			try {
				appsClassLoader.addUrl(jar.toURI().toURL());
				AnnotationScanner.addPath(jar);
			} catch (MalformedURLException e1) {
				e1.printStackTrace();
			} catch (NotFoundException e) {
				e.printStackTrace();
			}

		}
		Vector<Class> deployableClasses = findDeployableClasses(jar,
				Module.class, appsClassLoader);
		Application application = new Application();
		Properties jndiProps = null;
		try {
			jndiProps = findJNDIProperties(jar);
		} catch (IOException e) {
			System.err.println("Error loading jndi.properties from "
					+ jar.getName() + ".\nApplication will use default properties.");
			e.printStackTrace();
		}
		application.setJndiProperties(jndiProps);
		application.setName(jar.getName());
		for (Class<?> c : deployableClasses) {
			AppModule module = moduleDeployer.deployModule(c);
			if (module.containsActions()) {
				application.addModule(module);
				if (instantiate) {
					application.instantiateModule(module);
					module.validateActions();
				}
			}
		}
		if (application.containsModules()) {
			apps.addApplication(application.getName(), application);
			return true;
		}
		return false;
	}

	public boolean undeployApplication(String name) {
		if (apps.getApplication(name) != null) {
			apps.removeApplication(name);
			for (File f : appsJarFiles) {
				if (f.getName().equals(name)) {
					appsJarFiles.remove(f);
					return true;
				}
			}
		}
		return false;
	}

	public String getAppsScanPath() {
		return appsScanPath;
	}

	public void setAppsScanPath(String scanPath) {
		this.appsScanPath = scanPath;
		File scanDir = new File(scanPath);
		scan(scanDir, appsJarFiles);
		int size = appsJarFiles.size();
		URL[] urls = new URL[size];
		for (int i = 0; i < size; i++) {
			try {
				urls[i] = appsJarFiles.elementAt(i).toURI().toURL();
			} catch (MalformedURLException e) {
				e.printStackTrace();
			}
		}
		if (appsClassLoader == null) {
			appsClassLoader = new EU4RIAClassLoader(urls);
		} else {
			for (URL url : urls) {
				appsClassLoader.addUrl(url);
			}
		}
	}

	public EU4RIAClassLoader getAppsClassLoader() {
		return appsClassLoader;
	}

	public void setAppsClassLoader(EU4RIAClassLoader appsClassLoader) {
		this.appsClassLoader = appsClassLoader;
	}

	private void scan(File scanDir, Vector<File> jarVector) {
		File[] contents = scanDir.listFiles();
		for (File entry : contents) {
			if (entry.isDirectory()) {
				scan(entry, jarVector);
			} else if (entry.getAbsolutePath().endsWith(".jar")) {
				try {
					AnnotationScanner.addPath(entry);
					jarVector.addElement(entry);
				} catch (NotFoundException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private Vector<Class> findDeployableClasses(File jar, Class annotation,
			EU4RIAClassLoader loader) {
		JarFile jarFile = null;
		loader.importArchive(jar);
		try {
			jarFile = new JarFile(jar);
		} catch (IOException e) {
			e.printStackTrace();
		}
		Enumeration<JarEntry> entries = jarFile.entries();
		Vector<Class> deployableClasses = new Vector<Class>();
		while (entries.hasMoreElements()) {
			JarEntry entry = entries.nextElement();
			String name = entry.getName();
			if (name.endsWith(".class")) {
				name = name.substring(0, name.indexOf(".class"));
				name = name.replaceAll(File.separator, ".");
				try {
					Class c = loader.loadClass(name);
					if (AnnotationScanner.isAnnotationPresent(c, annotation)) {
						deployableClasses.addElement(c);
					}
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
		}
		return deployableClasses;
	}

	private Properties findJNDIProperties(File jar) throws IOException {
		JarFile jarFile = null;
		jarFile = new JarFile(jar);
		Properties props;
		JarEntry entry = jarFile.getJarEntry("jndi.properties");
		if (entry == null) {
			return null;
		} else {
			props = new Properties();
			props.loadFromXML(jarFile.getInputStream(entry));
			return props;
		}

	}

}
